// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: lucasyoo

#include "coin2/strategy/hamm/hamm.h"

#include "coin2/base/proto_util.h"
#include "coin2/base/string_util.h"
#include "coin2/exchange/base/log/latency_recorder.h"

namespace coin2::strategy::hamm {

uint64_t GetProductId(const IProduct& product) {
  return std::hash<std::string>()(
      product.absolute_norm() + "/" + product.exchange_name() + "/" + product.market_name());
}

//------------------------------------
// HammStrategy
//------------------------------------
HammStrategy::HammStrategy(
    const std::string& config_file,
    const std::optional<int64_t> strategy_created_time)
    : strategy_created_time_(
          strategy_created_time.has_value() ? *strategy_created_time : GetCurrentTimestamp()) {
  std::string content = ReadFileContent(config_file);
  LOG(INFO) << "strategy config json: \n" << content;
  CHECK(JsonStringToProto(content, &config_)) << content;
  LOG(INFO) << "strategy config proto: \n" << config_.DebugString();
  arb_tolerance_bps_ = config_.arb_tolerance_bps();
  RiskManagerConfig risk_config;
  risk_config.set_rest_error_window_size_sec(config_.rest_error_window_size_sec());
  risk_manager_ = std::make_unique<RiskManager>(risk_config);
  auto* agg_controller_config = config_.mutable_agg_controller_config();
  for (const auto& [key, subconfig] : config_.subconfig()) {
    auto strat = std::make_unique<HammSubStrategy>(
        key,
        subconfig,
        config_.mutable_pricer_config(),
        agg_controller_config,
        strategy_created_time_,
        config_.randomize_order(),
        config_.order_update_period_sec(),
        config_.max_posting_period_sec(),
        config_.fill_cooldown_period_sec());
    strat->SetRiskManager(risk_manager_.get());
    const auto product_id = GetProductId(*strat->Product());
    strats_.emplace(product_id, std::move(strat));
    agg_on_off_map_.emplace(product_id, false);
  }
  for (auto&& strat : strats_) {
    reserve_map_[strat.second->Product()] = strat.second->Reserve();
    ordered_strats_[strat.second->ArbOrder()] = strat.second.get();
  }
}

void HammStrategy::UpdateSimConfig(std::string name, SimConfig* config) {
  for (const auto& p : reserve_map_) {
    if (p.first->exchange_name() == name) {
      LOG(INFO) << "reserve symbols: " << p.first->exchange_name() << " "
                << p.first->absolute_norm() << " : " << p.second;
      config->add_reserve_symbols(p.first->absolute_norm());
      config->add_reserve_positions(p.second);
      config->set_start_timestamp(strategy_created_time_);
    }
  }
}

void HammStrategy::Init(Driver* driver) {
  LOG(INFO) << "HammStrategy::Init";
  driver_ = driver;
  for (auto&& strat : strats_) {
    strat.second->Init(driver);
    reserve_map_[strat.second->Product()] = strat.second->Reserve();
  }
  auto interval = driver->strategy()->config().pnl_report_period_ns();
  if (interval > 0) {
    pnl_monitor_ =
        std::make_unique<PnlMonitor>(driver->live_executor(), driver->archive_executor(), interval);
    pnl_monitor_->StartAsync();
  }

  strat_reporter_ = std::make_unique<StrategyReporter2>(driver->strategy()->config());
  for (auto& pair : strats_) {
    auto& strat = pair.second;
    const auto& exe_config = strat->ExecutorConfig();
    const IProduct* product = strat->Product();
    if (!strat->DryRun()) {
    const auto& request = strat->os()->GetAccountRequest();
      strat_reporter_
          ->RegisterExecutorConfig(strategy_created_time_, request, *product, exe_config);
    }
  }
  feed_timer_ = std::make_unique<DeadlineTimer>();
  feed_timer_->ExpiresEvery(config_.feed_timer_sec() * 1e9);
}

void HammStrategy::onBookFeed(const FeedUpdate& upd) {
  auto product_id = GetProductId(upd.product());
  auto it = strats_.find(product_id);
  if (it == strats_.end()) {
    LOG(ERROR) << "strategy not found: " << upd.product().base().symbol();
    return;
  }
  // 1. Update MidP of that Substrategy, Update bid price of pairs(for aggression)
  it->second->Pricer()->UpdateMidP(upd);
  if (it->second->Pricer()->Bbo().first > 0.0)
    bid_map_[product_id] = it->second->Pricer()->Bbo().first;

  // 2. Compare Every Market and Check the Arbitrage Condition
  if (strats_.size() > 1) {
    for (auto it2 = ordered_strats_.begin(); it2 != ordered_strats_.end(); ++it2) {
      it2->second->ResetArbFreeBbo();
    }
    // So, Compare Time: n Combination 2 (nC2) times
    for (auto it2 = ordered_strats_.begin(); it2 != ordered_strats_.end(); ++it2) {
      for (auto it3 = std::next(it2); it3 != ordered_strats_.end(); ++it3) {
        if (it2->second->Product()->market() == it3->second->Product()->market()) {
          ArbitrageCheck(it2->second, it3->second, upd.timestamp());
        }
      }
    }
  }
  // 3. Check highest_bid pair for aggression
  if (bid_map_.size() == strats_.size()) {
    double highest_bid = 0.0;
    uint64_t highest_product_id = 0;
    for (auto& pair : bid_map_) {
      if (pair.second > highest_bid) {
        highest_bid = pair.second;
        highest_product_id = pair.first;
      }
      agg_on_off_map_[pair.first] = false;
    }
    agg_on_off_map_[highest_product_id] = true;
  } else {  // Every exchanges' bbo are not updated -> no aggression
    agg_on_off_map_[product_id] = false;
  }
  // 4. Pricing and mm
  it->second->onBookFeed(upd, agg_on_off_map_[product_id]);
}

void HammStrategy::ArbitrageCheck(HammSubStrategy* strat1, HammSubStrategy* strat2, int64_t ts) {
  // Arbitrage Condition Between
  // Symbol A and Symbol B (Need to Escape): BidPrice(A) > AskPrice(B) || AskPrice(A) < BidPrice(B)
  if (!strat1 || !strat2) {
    return;
  }
  SPDLOG_INFO_EVERY_NS(
      600e9,
      ts,
      fmt::format(
          "ArbitrageCheck: {} {} {} {}",
          strat1->Product()->exchange_name(),
          strat1->Product()->absolute_norm(),
          strat2->Product()->exchange_name(),
          strat2->Product()->absolute_norm()));
  const std::pair<double, double>& strat1_bbo = strat1->Bbo();
  const std::pair<double, double>& strat2_bbo = strat2->Bbo();
  const int64_t strat1_arb_order = strat1->ArbOrder();
  const int64_t strat2_arb_order = strat2->ArbOrder();
  CHECK_GE(strat1_arb_order, 1);
  CHECK_GE(strat2_arb_order, 1);
  // If bbo was not updated, skip this check
  if (strat1_bbo.first == 0.0 || strat1_bbo.second == 0.0 || strat2_bbo.first == 0.0 ||
      strat2_bbo.second == 0.0) {
    return;
  }
  // strat 2 must be like strat 1 (ADJUST strat2)
  if (strat1_arb_order < strat2_arb_order) {
    // If Opportunity exists
    if (strat1_bbo.first >= strat2_bbo.second * (1 + arb_tolerance_bps_ * 1e-4) ||
        strat1_bbo.second * (1 + arb_tolerance_bps_ * 1e-4) <= strat2_bbo.first) {
      std::pair<double, double> arb_free_bbo1 = strat1_bbo;
      std::pair<double, double> arb_free_bbo2 = strat2_bbo;
      std::pair<bool, bool> is_adjust1{false, false};
      std::pair<bool, bool> is_adjust2{false, false};
      if (strat1_bbo.first >= strat2_bbo.second * (1 + arb_tolerance_bps_ * 1e-4)) {
        arb_free_bbo2.second = strat1_bbo.first;
        arb_free_bbo1.first = strat2_bbo.second;
        is_adjust2.second = true;
        is_adjust1.first = true;
        LOG(INFO) << "Arbitrage Opportunity: SELL(" << strat1->Product()->exchange_name() << " "
                  << strat1->Product()->absolute_norm() << ") BUY("
                  << strat2->Product()->exchange_name() << " " << strat2->Product()->absolute_norm()
                  << ") " << strat1_bbo.first << " " << strat2_bbo.second;
      } else if (strat1_bbo.second * (1 + arb_tolerance_bps_ * 1e-4) <= strat2_bbo.first) {
        arb_free_bbo2.first = strat1_bbo.second;
        arb_free_bbo1.second = strat2_bbo.first;
        is_adjust2.first = true;
        is_adjust1.second = true;
        LOG(INFO) << "Arbitrage Opportunity: BUY(" << strat1->Product()->exchange_name() << " "
                  << strat1->Product()->absolute_norm() << ") SELL("
                  << strat2->Product()->exchange_name() << " " << strat2->Product()->absolute_norm()
                  << ") " << strat1_bbo.second << " " << strat2_bbo.first;
      } else {
        NOTREACHED() << "ERROR WITH CONDITION HANDLING";
      }
      strat1->SetArbFreeBbo(arb_free_bbo1, is_adjust1);
      strat2->SetArbFreeBbo(arb_free_bbo2, is_adjust2);
    } else {
      SPDLOG_INFO_EVERY_NS(
          600e9,
          ts,
          fmt::format(
              "No Arbitrage Opportunity: {} {} {} {}",
              strat1->Product()->exchange_name(),
              strat1->Product()->absolute_norm(),
              strat2->Product()->exchange_name(),
              strat2->Product()->absolute_norm()));
    }
    // strat 1 must be like strat 2 (ADJUST strat1)
  } else if (strat1_arb_order > strat2_arb_order) {
    if (strat2_bbo.first >= strat1_bbo.second * (1 + arb_tolerance_bps_ * 1e-4) ||
        strat2_bbo.second * (1 + arb_tolerance_bps_ * 1e-4) <= strat1_bbo.first) {
      std::pair<double, double> arb_free_bbo1 = strat1_bbo;
      std::pair<double, double> arb_free_bbo2 = strat2_bbo;
      std::pair<bool, bool> is_adjust1{false, false};
      std::pair<bool, bool> is_adjust2{false, false};
      if (strat2_bbo.first >= strat1_bbo.second * (1 + arb_tolerance_bps_ * 1e-4)) {
        arb_free_bbo1.second = strat2_bbo.first;
        arb_free_bbo2.first = strat1_bbo.second;
        is_adjust1.second = true;
        is_adjust2.first = true;
        LOG(INFO) << "Arbitrage Opportunity: BUY(" << strat1->Product()->exchange_name() << " "
                  << strat1->Product()->absolute_norm() << ") SELL("
                  << strat2->Product()->exchange_name() << " " << strat2->Product()->absolute_norm()
                  << ") " << strat1_bbo.second << " " << strat2_bbo.first;
      } else if (strat2_bbo.second * (1 + arb_tolerance_bps_ * 1e-4) <= strat1_bbo.first) {
        arb_free_bbo1.first = strat2_bbo.second;
        arb_free_bbo2.second = strat1_bbo.first;
        is_adjust1.first = true;
        is_adjust2.second = true;
        LOG(INFO) << "Arbitrage Opportunity: SELL(" << strat1->Product()->exchange_name() << " "
                  << strat1->Product()->absolute_norm() << ") BUY("
                  << strat2->Product()->exchange_name() << " " << strat2->Product()->absolute_norm()
                  << ") " << strat1_bbo.second << " " << strat2_bbo.first;
      } else {
        NOTREACHED() << "ERROR WITH CONDITION HANDLING";
      }
      strat1->SetArbFreeBbo(arb_free_bbo1, is_adjust1);
      strat2->SetArbFreeBbo(arb_free_bbo2, is_adjust2);
    } else {
      SPDLOG_INFO_EVERY_NS(
          600e9,
          ts,
          fmt::format(
              "No Arbitrage Opportunity: {} {} {} {}",
              strat1->Product()->exchange_name(),
              strat1->Product()->absolute_norm(),
              strat2->Product()->exchange_name(),
              strat2->Product()->absolute_norm()));
    }
  } else {
    NOTREACHED() << "MUST HAVE DIFFERENT ARBITRAGE ORDER! " << strat1->Product()->exchange_name()
                 << " " << strat1->Product()->absolute_norm() << " AND "
                 << strat2->Product()->exchange_name() << " " << strat2->Product()->absolute_norm();
  }
}

void HammStrategy::onTradeFeed(const FeedUpdate& upd) {
  strats_[GetProductId(upd.product())]->onTradeFeed(upd);
}

void HammStrategy::onAccountOrder(const OrderUpdate& upd) {
  if (!upd.is_product_order_info()) return;
  auto it = strats_.find(GetProductId(upd.product()));
  if (it == strats_.end()) {
    LOG(ERROR) << "strategy not found: " << upd.product().base().symbol();
    return;
  } else {
    it->second->onAccountOrder(upd);
  }
}

void HammStrategy::onAccountInfo(const OrderUpdate& upd) {
  auto& account_info = upd.account_info();
  if (!account_info.is_ready()) {
    LOG(INFO) << "account not ready";
    return;
  }
  for (auto& strat : strats_) {
    strat.second->onAccountInfo(upd);
  }
  if (strat_reporter_) {
    strat_reporter_->onAccountInfo(upd);
    for (const auto& strat : strats_) {
      if (strat.second->DryRun()) {
        continue;
      }
      auto* prod = strat.second->Product();
      double price = strat.second->MidP();
      if (price > 1e-8) strat_reporter_->UpdateMidp(*prod, price);
    }
  }
}

void HammStrategy::onOrderLog(const coin::proto::OrderGatewayLog& order_log) {
  if (!order_log.has_account_request()) {
    return;
  }
  if (strat_reporter_) {
    strat_reporter_->GetMutableStrategyLogger()->WriteOrderLog(
        order_log,
        true,
        GetCurrentTimestamp());
  }
}

void HammStrategy::onOrderSubsystemError(const OrderUpdate& upd) {
  LOG(INFO) << "OrderSubsystemError: " << coin::proto::OrderUpdateType_Name(upd.update_type());
  LOG(INFO) << "OrderSubsystemError: " << upd.timestamp();
  risk_manager_->HandleOrderUpdate(upd);
}

void HammStrategy::onCleanupAux(Driver* driver, int num_retries) {
  int num_orders = 0;
  auto ts = GetCurrentTimestamp();
  for (auto& strat : strats_) {
    auto* executor = strat.second->Executor();
    if (executor) {
      executor->SetEnableOrderSubmission(false); 
      num_orders += executor->TryCancelAllWorkingOrders(ts);
    }
  }

  if (num_orders == 0) {
    LOG(INFO) << "Cleared all pending orders.";
    driver->PostTask([driver](){driver->Exit();});
  } else if (num_retries > 10) {
    LOG(WARNING) << "There may be some pendings orders left open.";
    driver->PostTask([driver](){driver->Exit();});
  } else {
    driver->PostDelayedTask(
      [this, driver, num_retries]() {
        this->onCleanupAux(driver, num_retries+1);
      }, 500'000'000LL);
  }
}

void HammStrategy::onCleanup(Driver* driver) {
  driver->SetImmediateExitOnSignal(false);
  driver->PostTask([this, driver](){ this->onCleanupAux(driver, 0); });
}

void HammStrategy::onHeartbeatFeed(const FeedTimer& timer) {
  SPDLOG_INFO_EVERY_NS(
      600e9,
      timer.Timestamp(),
      fmt::format("onHeartbeatFeed: {}", timer.Timestamp()));
  if (strats_.size() > 1) {
    for (auto it2 = ordered_strats_.begin(); it2 != ordered_strats_.end(); ++it2) {
      it2->second->ResetArbFreeBbo();
    }
    // So, Compare Time: n Combination 2 (nC2) times
    for (auto it2 = ordered_strats_.begin(); it2 != ordered_strats_.end(); ++it2) {
      for (auto it3 = std::next(it2); it3 != ordered_strats_.end(); ++it3) {
        if (it2->second->Product()->market() == it3->second->Product()->market()) {
          ArbitrageCheck(it2->second, it3->second, timer.Timestamp());
        }
      }
    }
  }
  for (auto& strat : strats_) {
    strat.second->onHeartbeatFeed(timer);
  }
  for (auto& order_system : driver_->order()->GetAllSystems()) {
    IFeedSubscriber* fsubog = dynamic_cast<IFeedSubscriber*>(order_system->gateway());
    if (fsubog) fsubog->onHeartbeatFeed(timer);
  }
}

//------------------------------------
// HammSubStrategy
//------------------------------------
HammSubStrategy::HammSubStrategy(
    const std::string& symbol,
    const HammSubStrategyConfig& config,
    HammPricerConfig* pricer_config,
    AggressiveControllerConfig* agg_controller_config,
    const int64_t& strategy_created_time,
    const bool& randomize_order,
    const double& order_update_period_sec,
    const double& max_posting_period_sec,
    const double& fill_cooldown_period_sec)
    : config_{config},
      agg_controller_config_{agg_controller_config},
      strategy_created_time_{strategy_created_time},
      arb_order_(config.arb_order()),
      randomize_order_(randomize_order),
      is_aggressive_(config.is_aggressive()) {
  product_ = CreateProductFromUniqueString(symbol, strategy_created_time_);
  LOG(INFO) << "add product: " << product_->exchange_name() << " " << product_->absolute_norm();
  pi_ = &(product_cache_.holder(*Product()).product_info());
  is_trading_futures_ = product_->market() == MarketType::Futures;
  std::string env_dry_run = getenv("HAMM_DRY_RUN") ? getenv("HAMM_DRY_RUN") : "";
  dry_run_ = config_.dry_run() || (env_dry_run == "1");

  // create passive executor config, aggressive executor config
  auto* pass_config = config_.mutable_pass_executor_config();
  pass_config->set_order_update_period(pass_config->order_update_period_sec() * 1e9);
  pass_config->set_max_posting_period(pass_config->max_posting_period_sec() * 1e9);
  pass_config->set_fill_cooldown_period(pass_config->fill_cooldown_period_sec() * 1e9);
  pass_config->set_symbol(product_->relative_norm());

  exec_config_ = *pass_config;
  min_bandwidth_bp_ = pricer_config->min_bandwidth_bp();
  max_bandwidth_bp_ = pricer_config->max_bandwidth_bp();
  layering_num_ = pricer_config->layering_num();
  lot_size_quote_ =
      config_.has_lot_size_quote() ? std::optional(config_.lot_size_quote()) : std::nullopt;

  pricer_ = std::make_unique<HammPricer>(
      pricer_config,
      exec_config_.reserve(),
      exec_config_.lot_size(),
      lot_size_quote_,
      exec_config_.min_pos(),
      exec_config_.max_pos(),
      strategy_created_time_);

  LOG(INFO) << "Create HammSubStrategy: \n"
            << "is_trading_futures: " << is_trading_futures_ << "\n"
            << "dry_run: " << dry_run_ << "\n"
            << config_.DebugString();
}

void HammSubStrategy::Init(Driver* driver) {
  LOG(INFO) << "HammSubStrategy::Init";
  driver_ = driver;
  if (!dry_run_) {
    os_ = driver_->order()->GetUniqueSystem(mea());
    auto get_product_holder =
        [this](const coin2::exchange::base::symbology::IProduct& product, int64_t timestamp) {
          return driver_->product_holder_manager()->GetProductHolder(product, timestamp);
        };
    LOG(INFO) << "PASSIVE EXECUTOR CONFIG: " << exec_config_.DebugString();
    executor_ = std::make_unique<LayeringExecutor2>(
        exec_config_,
        os(),
        strategy_created_time_,
        nullptr,
        get_product_holder,
        lot_size_quote_);
    LOG(INFO) << "AGG CONTROLLER CONFIG: " << agg_controller_config_->DebugString();
    agg_controller_ = std::make_unique<AggressiveController>(
        *agg_controller_config_,
        Executor(),
        exec_config_.order_update_period_sec());
  }
}

void HammSubStrategy::onTradeFeed(const FeedUpdate& upd) {
  if (GetProductId(upd.product()) != GetProductId(*Product())) {
    return;
  }
  pricer_->UpdateTrade(upd);
}

void HammSubStrategy::onBookFeed(const FeedUpdate& upd, const bool& is_agg_on) {
  const int64_t ts = upd.timestamp();
  timestamp_ = ts;
  if (!executor_ || !pricer_) return;
  if (!os_ || !os_->is_ready()) return;
  double pos = executor_->GetPosition();
  pricer_->UpdatePos(pos);
  auto book = upd.GetBookBuilder();
  if (book->AskSize() == 0 || book->BidSize() == 0) return;
  // Update Optimal Bid/Ask Price and order qty
  pricer_->onBookFeed(upd);
  auto& bbo = Bbo();
  if (is_aggressive_)
    agg_controller_->UpdateData(ts, pricer_->Volatility(), bbo, pricer_->OptimalBidAsk());

  if (!dry_run_) {
    if (IsClose(max_buy_price_, bbo.first) || IsClose(min_sell_price_, bbo.second)) {
      return;
    }

    const auto& tick_price_policy = pi_->tick_price();
    const auto& tick_qty = pi_->tick_qty();

    auto& order_bid_prices = pricer_->GetOrderQtyGenerator()->BidPrices();
    auto& order_ask_prices = pricer_->GetOrderQtyGenerator()->AskPrices();
    auto& order_bid_qtys = pricer_->GetOrderQtyGenerator()->BidQtys();
    auto& order_ask_qtys = pricer_->GetOrderQtyGenerator()->AskQtys();

    if (order_bid_prices.size() > 0 && order_ask_prices.size() > 0 && order_bid_qtys.size() > 0 &&
        order_ask_qtys.size() > 0) {
      int i = 0;
      auto bid =
          tick_price_policy.RoundPassively(order_bid_prices[i], OrderDirection::DIRECTION_BUY);
      auto ask =
          tick_price_policy.RoundPassively(order_ask_prices[i], OrderDirection::DIRECTION_SELL);
      auto bid_qty =
          std::max({tick_qty.RoundPassively(order_bid_qtys[i], OrderDirection::DIRECTION_BUY),
                    layering_num_ * tick_qty.submittable_min_qty(bid),
                    config_.min_lot_size_quote() / bid});
      auto ask_qty =
          std::max({tick_qty.RoundPassively(order_ask_qtys[i], OrderDirection::DIRECTION_SELL),
                    layering_num_ * tick_qty.submittable_min_qty(ask),
                    config_.min_lot_size_quote() / ask});
      executor_->UpdateLotSize(std::max(bid_qty, ask_qty));
      LOG(INFO) << fmt::format(
          "{} {}-{} ManageMmOrders: bid: {}, ask: {}, bid_qty: {}, ask_qty: {}",
          upd.timestamp(),
          upd.product().exchange_name(),
          upd.product().absolute_norm(),
          bid,
          ask,
          bid_qty,
          ask_qty);
      CHECK_GT(bid, 0);
      CHECK_GT(ask, 0);
      // latest_order_info_ = std::make_tuple(ask, bid, ask_qty, bid_qty);
      latest_order_info_[0] = ask;
      latest_order_info_[1] = bid;
      latest_order_info_[2] = ask_qty;
      latest_order_info_[3] = bid_qty;
      ManageMmOrders(upd, ask, bid, ask_qty, bid_qty, false, false, is_agg_on);
    }
  }
}

void HammSubStrategy::onAccountInfo(const OrderUpdate& upd) {
  if (!executor_) return;
  auto& account_info = upd.account_info();

  if (!account_info.is_ready()) {
    return;
  }
  if (!dry_run_) {
    if (is_trading_futures_ && account_info.HasPosition(*Product())) {
      const auto& prod = *Product();
      if (account_info.HasPosition(prod)) {
        executor_->onAccountInfo(upd);
      }
    } else {
      const auto& base = Product()->base();
      if (account_info.HasBalance(base)) {
        executor_->onAccountInfo(upd);
      }
    }
  }
  if (os_ && os_->is_ready()) {
    auto print_func = [&]() {
      std::ostringstream ss;
      if (exec_config_.has_mea() && exec_config_.has_symbol()) {
        ss << exec_config_.mea() << " " << exec_config_.symbol();
      } else {
        ss << "UNKNOWN";
      }
      ss << driver_->order()->GetUniqueSystem(mea())->gateway()->order_manager().DebugString();
      return ss.str();
    };
    if (upd.timestamp() - order_manager_log_ts_ > 60e9) {
      order_manager_log_ts_ = upd.timestamp();
      LOG(INFO) << print_func();
    }
  }
}

void HammSubStrategy::onAccountOrder(const OrderUpdate& upd) {
  if (!upd.is_product_order_info()) return;
  if (!pricer_ || !executor_) return;
  if (!dry_run_) {
    executor_->onAccountOrder(upd);
    auto oe = upd.product_order_info().event();
    // LOG(INFO) << oe.DebugString();
    if (oe.type() == OrderEvent::ORDER_ACCEPTED) {
      if (oe.trade_side() == coin::proto::TRADE_BUY_SIDE) {
        max_buy_price_ = oe.order_price();
      } else if (oe.trade_side() == coin::proto::TRADE_SELL_SIDE) {
        min_sell_price_ = oe.order_price();
      } else {
        NOTREACHED() << "NO TRADE_SIDE() INFO";
      }
    } else if (oe.type() == OrderEvent::CANCEL_CONFIRMED) {
      if (oe.trade_side() == coin::proto::TRADE_BUY_SIDE) {
        max_buy_price_ = 0;
      } else if (oe.trade_side() == coin::proto::TRADE_SELL_SIDE) {
        min_sell_price_ = 0;
      } else {
        NOTREACHED() << "NO TRADE_SIDE() INFO";
      }
    } else if (oe.type() == OrderEvent::ORDER_FILLED) {
      if (oe.fill_qty() > 0) {
        if (oe.trade_side() == coin::proto::TRADE_BUY_SIDE) {
          if (oe.fully_filled()) {
            max_buy_price_ = 0;
          }
          pricer_->UpdateFillInfo(oe.fill_price(), oe.fill_qty(), upd.timestamp(), "BUY");
        } else if (oe.trade_side() == coin::proto::TRADE_SELL_SIDE) {
          max_buy_price_ = 0;
          if (oe.fully_filled()) {
            min_sell_price_ = 0;
          }
          pricer_->UpdateFillInfo(oe.fill_price(), oe.fill_qty(), upd.timestamp(), "SELL");
        } else {
          NOTREACHED() << "NO TRADE_SIDE() INFO";
        }
      }
    }
  }
}

std::pair<double, double> HammSubStrategy::GenerateBandwidthBp() {
  double buy_bandwidth_bp = std::min(
      min_bandwidth_bp_ *
          std::exp(std::max(
              pricer_->GetFillCollector()->BuyQty() / pricer_->GetClippedLotSize(),
              pricer_->GetFillCollector()->BuyNum() / pricer_->config().fill_num_hurdle())),
      max_bandwidth_bp_);

  double sell_bandwidth_bp = std::min(
      min_bandwidth_bp_ *
          std::exp(std::max(
              pricer_->GetFillCollector()->SellQty() / pricer_->GetClippedLotSize(),
              pricer_->GetFillCollector()->SellNum() / pricer_->config().fill_num_hurdle())),
      max_bandwidth_bp_);
  return std::make_pair(buy_bandwidth_bp, sell_bandwidth_bp);
}

void HammSubStrategy::ManageMmOrders(
    const FeedUpdate& upd,
    double ask,
    double bid,
    double ask_qty,
    double bid_qty,
    bool is_cancel_all_ask,
    bool is_cancel_all_bid,
    const bool& is_agg_on) {
  const auto& ts = upd.timestamp();
  os_->gateway()->UpdateTimeForSim(ts);
  // executor_->ManageMmOrders(ts, ask, bid, ask_qty, bid_qty, is_cancel_all_ask,
  // is_cancel_all_bid);
  executor_->UpdateState(ts);
  executor_->ManageRisk(ts);
  executor_->UpdateMidP(pricer_->MidP());
  if (is_aggressive_ && is_agg_on) agg_controller_->ManageAggOrders(upd);
  auto [buy_bandwidth_bp, sell_bandwidth_bp] = GenerateBandwidthBp();
  executor_->ManageSideOrders(
      ts,
      1,
      order_executor::LayeringLevels::EqualSize2(
          pi_,
          layering_num_,
          1,
          bid / (1 + buy_bandwidth_bp * 1e-4),
          bid,
          bid_qty,
          config_.min_lot_size_quote(),
          randomize_order_));
  executor_->ManageSideOrders(
      ts,
      -1,
      order_executor::LayeringLevels::EqualSize2(
          pi_,
          layering_num_,
          -1,
          ask,
          ask * (1 + sell_bandwidth_bp * 1e-4),
          ask_qty,
          config_.min_lot_size_quote(),
          randomize_order_));
}

void HammSubStrategy::SetArbFreeBbo(
    std::pair<double, double> bbo,
    std::pair<bool, bool> is_adjust) {
  pricer_->SetArbFreeBbo(bbo, is_adjust);
}

void HammSubStrategy::ResetArbFreeBbo() { pricer_->ResetArbFreeBbo(); }

void HammSubStrategy::onHeartbeatFeed(const FeedTimer& timer) {
  const auto& [ask, bid, ask_qty, bid_qty] = latest_order_info_;
  const int64_t ts = timer.Timestamp();
  if (!dry_run_) {
    executor_->UpdateState(ts);
    executor_->ManageRisk(ts);
    os_->gateway()->UpdateTimeForSim(ts);
    auto [buy_bandwidth_bp, sell_bandwidth_bp] = GenerateBandwidthBp();
    if (bid > 0 && bid_qty > 0) {
      executor_->ManageSideOrders(
          ts,
          1,
          order_executor::LayeringLevels::EqualSize2(
              pi_,
              layering_num_,
              1,
              bid / (1 + buy_bandwidth_bp * 1e-4),
              bid,
              bid_qty,
              config_.min_lot_size_quote(),
              randomize_order_));
    }
    if (ask > 0 && ask_qty > 0) {
      executor_->ManageSideOrders(
          ts,
          -1,
          order_executor::LayeringLevels::EqualSize2(
              pi_,
              layering_num_,
              -1,
              ask,
              ask * (1 + sell_bandwidth_bp * 1e-4),
              ask_qty,
              config_.min_lot_size_quote(),
              randomize_order_));
    }
  }
}

}  // namespace coin2::strategy::hamm
